package com.xuzhiguang.lightnat.server.core.server.transfer;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import com.xuzhiguang.lightnat.common.codec.NatFrameDecoder;
import com.xuzhiguang.lightnat.common.codec.NatFrameEncoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageDecoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageEncoder;
import com.xuzhiguang.lightnat.common.message.MessageProcessorFactory;
import com.xuzhiguang.lightnat.common.message.MessageProcessorHandler;
import com.xuzhiguang.lightnat.server.core.client.NatClientService;
import com.xuzhiguang.lightnat.server.core.server.Server;
import com.xuzhiguang.lightnat.server.core.server.transfer.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
public class TransferServer implements Server {

    private NatClientService natClientService;

    private MessageProcessorFactory messageProcessorFactory;

    private TransferServerProperties transferServerProperties;

    private EventLoopGroup bossGroup;

    private EventLoopGroup workerGroup;

    private Channel channel;

    private TransferServer() {

    }

    public static class Builder {

        private TransferServer transferServer;

        public Builder() {
            this.transferServer = new TransferServer();
        }

        public Builder natClientService(NatClientService natClientService) {
            this.transferServer.setNatClientService(natClientService);
            return this;
        }

        public Builder messageProcessorFactory(MessageProcessorFactory messageProcessorFactory) {
            this.transferServer.setMessageProcessorFactory(messageProcessorFactory);
            return this;
        }

        public Builder transferServerProperties(TransferServerProperties transferServerProperties) {
            this.transferServer.setTransferServerProperties(transferServerProperties);
            return this;
        }

        public TransferServer build() {
            return this.transferServer;
        }
    }


    @Override
    public void start() throws InterruptedException {

        checkProperties();

        bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("t-boss"));
        workerGroup = new NioEventLoopGroup(new DefaultThreadFactory("t-worker"));
        ServerBootstrap bootstrap = new ServerBootstrap();

        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
        MessageProcessorHandler messageProcessorHandler = new MessageProcessorHandler(this.messageProcessorFactory);
        AuthenticationHandler authenticationHandler = new AuthenticationHandler(this.natClientService);
        InactiveHandler inactiveHandler = new InactiveHandler();
        WritabilityChangedHandler writabilityChangedHandler = new WritabilityChangedHandler();
        ExceptionHandler exceptionHandler = new ExceptionHandler();

        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 512)
                .childOption(ChannelOption.SO_REUSEADDR, true)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .handler(loggingHandler)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast("idleCheckHandler", new IdleCheckHandler())
                                .addLast("natFrameDecoder", new NatFrameDecoder())
                                .addLast("natFrameEncoder", new NatFrameEncoder())
                                .addLast("natMessageDecoder", new NatMessageDecoder())
                                .addLast("natMessageEncoder", new NatMessageEncoder())
                                .addLast("authHandler", authenticationHandler)
                                .addLast("inactiveHandler", inactiveHandler)
                                .addLast("writabilityChangedHandler", writabilityChangedHandler)
                                .addLast("messageProcessorHandler", messageProcessorHandler)
                                .addLast("exceptionHandler", exceptionHandler);
                    }
                });

        ChannelFuture f;
        if ("*".equals(transferServerProperties.getTransferBindHost())) {
            f = bootstrap.bind(transferServerProperties.getTransferBindPort()).sync();
        } else {
            f = bootstrap.bind(transferServerProperties.getTransferBindHost(), transferServerProperties.getTransferBindPort()).sync();
        }
        channel = f.channel();
        log.info("transfer server started success. host:{}, port:{}",
                transferServerProperties.getTransferBindHost(), transferServerProperties.getTransferBindPort());
    }

    @Override
    public void stop() {
        channel.close();
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

    private void checkProperties() {
        if (StrUtil.isBlank(transferServerProperties.getTransferBindHost())) {
            throw new IllegalArgumentException("transfer server host is black.");
        }
        if (!NetUtil.isValidPort(transferServerProperties.getTransferBindPort())) {
            throw new IllegalArgumentException("transfer server port is illegal. server port:" + transferServerProperties.getTransferBindPort());
        }

    }

}
